package com.onpositive.knowledge.model.values;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;

import com.onpositive.knowledge.model.value.audits.AbstractAuditResult;
import com.onpositive.knowledge.model.value.audits.GeneralAuditResult;
import com.onpositive.knowledge.model.value.audits.IAuditManager;
import com.onpositive.knowledge.model.values.ReplacementProposals.Proposal;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.units.ParsedIdent;
import com.onpositive.units.ParsedSentence;
import com.onpositive.units.ParsedString;
import com.onpositive.units.ParsedValue;
import com.onpositive.units.ParsedValueSequence;
import com.onpositive.units.ParsedValueSequenceV2;
import com.onpositive.units.ValueCluster;
public class ReplacementTransformer extends AuditAssistedTransformer {

	private static final long serialVersionUID = -4755276164516503480L;
	public static final String ID = "replacement-transformer" ; 

	public ReplacementTransformer(IHasAuditResult src,String auditId) {
		super(src);
		this.auditId = auditId;
	}
	
	private final String auditId;
	
	ModifyingValueTraverser traverser;

	@Override
	public DescribedValue[] adjust(Object object, IProperty prop, DescribedValue... original) {
		
		init();

		String name = prop.getMeta().getSingleValue( "id", String.class, null ) ;	
		
		AbstractAuditResult aResult = this.getAuditResultSource().getAuditResult() ;
		
		List<ReplacementProposals> proposals = null ;
		
		if( aResult instanceof ReplacementProposals ){
			proposals = new ArrayList<ReplacementProposals>() ;
			proposals.add( (ReplacementProposals) aResult ) ;
		}		
		else if( aResult instanceof GeneralAuditResult )
		{
			GeneralAuditResult gResult = (GeneralAuditResult) aResult ;
			proposals = gResult.getResults( prop.getId(), auditId, ReplacementProposals.class ) ;			
		}
		
		if( proposals == null || proposals.size() == 0 )
			return original ;
//		if( proposals != null && proposals.size() != 0 )			
//			return doAdjust( proposals, original );
		
		HashMap<String,Object> meta = new HashMap<String, Object>() ;
		meta.put("proposals", proposals) ;
		
		if( proposals == null || proposals.size() == 0 )
			return original ;
		
		Object processed = traverser.iterateValues(null, original, false, meta, 0, false);
		if( !(processed instanceof Object[]) )
			return original ;
		
		Object[] arr = (Object[]) processed ;
		ArrayList<DescribedValue> lst = new ArrayList<DescribedValue>() ; 
		for( Object o : arr ){
			if( o instanceof DescribedValue )
				lst.add((DescribedValue) o) ;
			else
				lst.add( null ) ;
		}
		
		return lst.toArray( new DescribedValue[lst.size()] ) ;
	}

	private void init() {
		
		Class<?>[] classes
			= new Class<?>[]{ ParsedSentence.class, ValueCluster.class, ParsedString.class };
		
		IHasAuditResult auditResultSource = getAuditResultSource();
		if(auditResultSource instanceof IAuditManager){
			IAuditManager am = (IAuditManager) auditResultSource;
			Class<?>[] targetClasses = am.getTargetClasses(auditId);
			if(targetClasses!=null)
				classes = targetClasses;
		}
		
		final Class<?>[] tClasses = classes;

	traverser = new ModifyingValueTraverser() {
		
		@SuppressWarnings("unchecked")
		@Override
		protected Object process(IProperty prop, Object val, Map<String, Object> meta, int index, boolean cameFromCollection )
		{
			Object obj = meta.get("proposals") ;
			List<ReplacementProposals> proposals = (List<ReplacementProposals>) obj ;
			
			if( val instanceof ParsedString )
			{
				ParsedString ps = (ParsedString) val ;				
				ParsedValue newVal = tryProposals(proposals, ps);				
				return newVal ;				
			}
			if( val instanceof ParsedValueSequenceV2 )
			{
				ParsedValueSequenceV2 pvsv2 = (ParsedValueSequenceV2) val ;				
				Object newVal = processSequence( pvsv2, prop, meta ) ;
				return newVal ;					
			}
			else
				return null;			
		}
		
		private Object processSequence( ParsedValueSequenceV2 pvsv2, IProperty prop, Map<String, Object> meta )
		{			
			Object obj = meta.get("proposals") ;
			List<ReplacementProposals> proposals = (List<ReplacementProposals>) obj ;
			
			ParsedValue newVal = tryProposals(proposals, pvsv2) ;
			if( newVal != null )
				return newVal ;
			
			ParsedValue[] valArr = pvsv2.getValuesArray() ;
			ParsedValue[] newValArr = (ParsedValue[]) iterateValues(prop, valArr, false, meta, 0, false) ;
			
			String comment = ID+pvsv2.getComment();
			ParsedValue newPv = (ParsedValue) pvsv2.clone( newValArr, comment) ;
			
			return newPv ;
		}
		
		@Override
		protected Class<?>[] createTargetClasses() {
			return tClasses; 
		}
		
		@Override
		protected Class<?>[] createNecessaryClasses() {
			return tClasses;
		}
	};
	
	}	
	
	private ParsedValue tryProposals(List<ReplacementProposals> proposals,	ParsedValue ps)
	{
//		String auditResultComment = null ;
		Proposal best = new Proposal() ;
		byte infered = 0;
		for( ReplacementProposals rp : proposals )
		{
			Proposal proposal = rp.getBestProposal( ps ) ;
			if( proposal == null )
				continue ;
			
			int weight = proposal.getWeight();
			if( proposal != null && best.getWeight() < weight && weight < rp.getValidityBarrier() )
			{
				best = proposal ;
//					auditResultComment = rp.getComment() ;					
			}				
		}
		
		if( !best.isNonTrivial() )
			return null ;
		
		//TODO make infered construction more clever
		infered = (byte) best.getWeight() ;
		Object value = best.getVlaue() ;
		if(!(value instanceof ParsedValue))
			return null ;
			

		ParsedValue proposalValue = (ParsedValue) value ;
		String comment = ID+ps.getComment();
		ParsedValue newVal = (ParsedValue) proposalValue.clone( proposalValue.value() , comment) ;
		
		return newVal ;
	}
	
	private DescribedValue[] doAdjust( Collection<ReplacementProposals> proposals, DescribedValue[] original) {
		
		ArrayList<DescribedValue> result = new ArrayList<DescribedValue>() ;
		
		for( DescribedValue dv : original )
		{
			Object valueObject = dv.value();
			if( valueObject == null || !(valueObject instanceof ParsedValueSequence) )
			{
				result.add(dv) ;
				continue ;
			}
			ParsedValueSequence pvs = (ParsedValueSequence) valueObject ;
			ParsedValueSequence new_pvs = new ParsedValueSequence( "rplc-transf " + pvs.getComment() ) ;
			
			Object parsedStringSet = pvs.value() ;
			if( parsedStringSet == null )
			{
				result.add(dv) ;
				continue ;
			}
			
			LinkedHashSet<ParsedString> set = (LinkedHashSet<ParsedString>) parsedStringSet ;
			
			boolean gotAChange = false ;
			String comment = null ;
			//TODO make infered construction more clever
			byte infered = 0;
			for( ParsedString ps : set )
			{
				String str = ps.getValueAsString() ;
				
				String auditResultComment = null ;
				Proposal best = new Proposal() ;
				for( ReplacementProposals rp : proposals )
				{
					Proposal proposal = rp.getBestProposal(str) ;
					if( proposal == null )
						continue ;
					
					int weight = proposal.getWeight();
					if( proposal != null && best.getWeight() < weight && weight < rp.getValidityBarrier() )
					{
						best = proposal ;
						auditResultComment = rp.getComment() ;					
					}				
				}
				
				if( best.isNonTrivial() )
				{
					//TODO make infered construction more clever
					infered = (byte) best.getWeight() ;
					Object value = best.getVlaue() ;
					if(!(value instanceof String)){
						new_pvs.addValue(ps) ;
						continue ;
					}
					
					String newStr = (String) value ;
					if( newStr.equals(str) ){
						new_pvs.addValue(ps) ;					
						continue ;
					}
					
					comment = dv.getComment() + ": " + ID + ": " + auditResultComment ;
					ParsedString new_ps = new ParsedString( newStr, "rplc-transf "+ps.getComment() ) ;	
					new_pvs.addValue(new_ps) ;
					gotAChange = true ;								
				}
				
			}
			if( gotAChange )
			{
				DescribedValue newDv = new DescribedValue( infered, new_pvs, null, comment ) ;
				result.add( newDv) ;				
			}
			else
				result.add(dv) ;
			
		}
		return result.toArray( new DescribedValue[result.size()] ) ;
	}

	@Override
	public String getId() {
		return ID;
	}

}
